/*
 * Copyright 2008 the Breachwalls OSS project and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.breachwalls.mogen.generator.template;

import java.io.File;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.breachwalls.mogen.model.GenerateMode;
import com.breachwalls.mogen.model.Model;
import com.breachwalls.mogen.model.ModelType;
import com.breachwalls.mogen.utils.JavaNameUtils;

/**
 * モデルテンプレートのサポートクラス.
 * @author todoken
 * @version $Revision: 31 $
 */
public class ModelTemplateSupport implements ModelTemplate {

    /**
     * シリアルバージョンUID.
     */
    private static final long serialVersionUID = 1L;

    /**
     * ログ.
     */
    private static Logger logger = LoggerFactory.getLogger(ModelTemplateSupport.class);

    /**
     * モデル.
     */
    private Model model;

    /**
     * ファイル出力先ベースディレクトリ.
     */
    private File base;

    /**
     * 自動生成するファイル.
     */
    private List<GenerateFile> generateFiles = new ArrayList<GenerateFile>();

    /**
     * インポートクラスを保持するコンテキスト.
     */
    private ImportContext importContext = new SortableImportContext();

    /**
     * コンストラクタ.
     * @param newBase ファイル出力先ベースディレクトリ
     * @param newModel モデルの定義
     */
    public ModelTemplateSupport(File newBase, Model newModel) {
        this.base = newBase;
        this.model = newModel;
    }

    /**
     * モデルを取得する.
     * @return モデル
     */
    public Model getModel() {
        return model;
    }

    /**
     * ファイル出力先ベースディレクトリを取得する.
     * @return ファイル出力先ベースディレクトリ
     */
    public File getBase() {
        return base;
    }

    /**
     * {@inheritDoc}
     */
    public Iterator<GenerateFile> iterateGenerateFiles() {
        return new Iterator<GenerateFile>() {

            private Iterator<GenerateFile> iterator = generateFiles.iterator();

            public boolean hasNext() {
                boolean hasNext = iterator.hasNext();
                if (hasNext) {
                    importContext.clear();
                }
                return hasNext;
            }

            public GenerateFile next() {
                return iterator.next();
            }

            public void remove() {
                iterator.remove();
            }

        };
    }

    /**
     * 自動生成するファイルの情報を追加する.
     * @param generateFileName 自動生成するファイル名
     * @param templateFileName テンプレートファイル名
     */
    public void addGenerateFile(String generateFileName, String templateFileName) {
        final GenerateMode mode = getModel().getGenerateMode();
        if (GenerateMode.NONE != mode) {
            File file = getGenerateFile(generateFileName);
            if (GenerateMode.OVERWRITE == mode || (GenerateMode.PROTECT == mode && !file.exists())) {
                generateFiles.add(new GenerateFile(file, templateFileName));
                if (logger.isDebugEnabled()) {
                    logger.debug("[" + file.getAbsolutePath() + "] add to generate candicate.");
                }
            }
        }
    }

    /**
     * 自動生成するファイルを取得する.<br>
     * また、出力先のディレクトリが存在しない場合は作成する.
     * @param generateFileName 自動生成するファイル名
     * @return 自動生成するファイル
     */
    public File getGenerateFile(final String generateFileName) {
        String packagename = StringUtils.replaceChars(getModel().getPackageName(), '.', '/');
        File dir = new File(getBase(), packagename);
        if (!dir.mkdirs()) {
            if (logger.isDebugEnabled()) {
                logger.debug("[" + dir.getAbsolutePath() + "] is already exists.");
            }
        }
        return new File(dir, generateFileName);
    }

    /**
     * Java のパッケージ文を生成する.
     * @return Java のパッケージ文
     */
    public String generateJavaPackageSentence() {
        String name = getModel().getPackageName();
        if (StringUtils.isNotEmpty(name)) {
            name = "package " + name + ";";
        }
        return name;
    }

    /**
     * Java のインポート文を生成する.
     * @return Java のインポート文
     */
    public List<String> generateJavaImportClassSentences() {
        final String[] imports = importContext.toArray();
        return new AbstractList<String>() {

            @Override
            public String get(int index) {
                String importClass = imports[index];
                if (StringUtils.isNotEmpty(importClass)) {
                    importClass = "import " + importClass + ";";
                }
                return importClass;
            }

            @Override
            public int size() {
                return imports.length;
            }

        };
    }

    /**
     * フルクラス名をコンテキストに登録し、インポート文に追加されるようにする.<br>
     * また、追加したクラス名を取得する.
     * @param fullname インポート文に追加するフルクラス名
     * @return インポート文に追加するクラスのクラス名
     */
    public String addImportFullClassName(final String fullname) {
        importContext.add(fullname);
        return ClassUtils.getShortClassName(fullname);
    }

    /**
     * モデルを元に、フルクラス名をコンテキストに登録し、インポート文に追加されるようにする.<br>
     * また、追加したクラス名を取得する.
     * @param newModel インポート文に追加するフルクラス名を持つモデル
     * @return インポート文に追加するクラスのクラス名
     */
    public String addImportFullClassName(final Model newModel) {
        return addImportFullClassName(newModel.getClassName());
    }

    /**
     * 自動生成するクラスのクラス宣言文を取得する.
     * @return 自動生成するクラスのクラス宣言文
     */
    public String getClassDefineSentence() {
        StringBuffer b = new StringBuffer();
        b.append("public ");
        if (getModel().isAbstractClass()) {
            b.append("abstract ");
        } else if (getModel().getModelType() == ModelType.ENUM) {
            b.append("final ");
        }
        b.append("class ");
        return b.toString();
    }

    /**
     * Java の継承文を生成する.
     * @return Java の継承文
     */
    public String generateExtendsClassSentence() {
        importContext.add(getModel().getExtendsClassName());
        if (StringUtils.isNotEmpty(getModel().getExtendsShortClassName())) {
            return " extends " + getModel().getExtendsShortClassName() + "";
        }
        return "";
    }

    /**
     * 自動生成するクラスのシリアルバージョンIDを算出する.
     * @return 自動生成するクラスのシリアルバージョンID
     */
    public String getSerialVersionUID() {
        // TODO 計算式が不明、つか自動生成できないかも
        return "1L";
    }

    /**
     * 自動生成するファクトリのクラス名を取得する.<br> {@link Model#getClassName()}の値＋Factoryとする.
     * @return 自動生成するファクトリのクラス名
     */
    public String getGenerateFactoryClassName() {
        return getModel().getShortClassName() + "Factory";
    }

    /**
     * 自動生成するプロパティエディタのクラス名を取得する.<br> {@link Model#getClassName()}の値＋Editorとする.
     * @return 自動生成するプロパティエディタのクラス名
     */
    public String getGenerateEditorClassName() {
        return getModel().getShortClassName() + "Editor";
    }

    /**
     * RDBの命名規則に従った名称やその他の名称をJavaのフィールド名称に変換する.
     * @param name 変換する名称
     * @return 変換した名称
     */
    public String toJavaFieldName(String name) {
        return JavaNameUtils.toJavaFieldName(name);
    }

    /**
     * Javaのプロパティ名称などをアクセッサメソッド形式の名前に変換する.
     * @param name 列挙型などのプロパティ名
     * @return アクセッサメソッド形式に変換した列挙型などのプロパティ名
     */
    public String toMethodName(String name) {
        return JavaNameUtils.toJavaMethodName(name);
    }

}
